package com.nowcoder.community.service;

import com.nowcoder.community.dao.elasticsearch.DiscussPostRepository;
import com.nowcoder.community.entity.DiscussPost;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class ElasticsearchService {

    @Autowired
    private DiscussPostRepository discussRepository;
    @Autowired
    private ElasticsearchTemplate elasticTemplate;

    //保存 帖子信息 到 es中
    public void saveDiscussPost(DiscussPost post) {
        discussRepository.save(post);
    }

    //es中删除帖子
    public void deleteDiscussPost(int id) {
        discussRepository.deleteById(id);
    }

    /**
     * 搜索 相关的帖子，进行高亮显示
     * @param keyword 搜索的内容
     * @param current 当前页
     * @param limit 每一页显示的数量
     * @return
     */
    public Page<DiscussPost> searchDiscussPost(String keyword, int current, int limit) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                //text 就是我们的输入栏的搜索，在 title 和 content 字段进行搜索
                .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
                //搜索之后进行的排序
                //先对 type（置顶） 类型进行倒序，然后 score（精华），然后是按照 时间 进行倒序
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                //因为可能一下子查询数据过多，所以的分页，不然浪费性能
                .withPageable(PageRequest.of(current, limit))
                //匹配到的字词进行加标签（高亮）
                .withHighlightFields(
                        new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).build();

        //将我们查找出来的数据，直接封装到 SearchResultMapper 中，我们直接对 这个 Mapper（含有高亮标签） 进行操作
        return elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
                //获取查询的数据 hit
                SearchHits hits = response.getHits();
                if (hits.getTotalHits() <= 0) {
                    return null;
                }

                //返回的 数据 进行加工，封装成 帖子 对象
                List<DiscussPost> list = new ArrayList<>();
                for (SearchHit hit : hits) {
                    //每一个 元素 就是一条帖子对象，所以每一次都需要进行 new 一个帖子对象
                    DiscussPost post = new DiscussPost();

                    //通过 JSON 格式的获取，get 获取的是 Object 先转换为 string 形式
                    String id = hit.getSourceAsMap().get("id").toString();
                    post.setId(Integer.valueOf(id));

                    String userId = hit.getSourceAsMap().get("userId").toString();
                    post.setUserId(Integer.valueOf(userId));

                    String title = hit.getSourceAsMap().get("title").toString();
                    post.setTitle(title);

                    String content = hit.getSourceAsMap().get("content").toString();
                    post.setContent(content);

                    String status = hit.getSourceAsMap().get("status").toString();
                    post.setStatus(Integer.valueOf(status));

                    //时间是 long 类型的
                    String createTime = hit.getSourceAsMap().get("createTime").toString();
                    post.setCreateTime(new Date(Long.valueOf(createTime)));

                    String commentCount = hit.getSourceAsMap().get("commentCount").toString();
                    post.setCommentCount(Integer.valueOf(commentCount));

                    // 处理高亮显示的结果
                    //查找 title 是否存在高亮的字段
                    HighlightField titleField = hit.getHighlightFields().get("title");
                    if (titleField != null) {
                        //显示 高亮那部分 位置 到title，而不是从头开始显示 title，这是 es 内置的功能
                        //因为，title 中可能有多处地方都是高亮的，我们只显示第一个地方的上下文，因为位置不够放，参考百度搜索
                        //存在的话，就将 第一个高亮的上下文显示
                        post.setTitle(titleField.getFragments()[0].toString());
                    }

                    //查看 content 内容是否存在高亮内容
                    HighlightField contentField = hit.getHighlightFields().get("content");
                    if (contentField != null) {
                        //内容也是显示第一个 高亮的位置 的上下文
                        post.setContent(contentField.getFragments()[0].toString());
                    }

                    list.add(post);
                }

                //这个返回是 固定的模式，参考就行
                return new AggregatedPageImpl(list, pageable,
                        hits.getTotalHits(), response.getAggregations(), response.getScrollId(), hits.getMaxScore());
            }
        });
    }

}
